{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Calibrate $\\pi$ Pulses\n",
    "*Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved.*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Outline\n",
    "\n",
    "This tutorial introduces how to calibrate a $\\pi$ pulse by varying the amplitude of the drive pulse. The outline of this tutorial is as follows:\n",
    "- Introduction\n",
    "- Preparation\n",
    "- Define the system Hamiltonian\n",
    "- Sweep amplitudes\n",
    "- Cosine regression\n",
    "- Summary"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Introduction\n",
    "\n",
    "Calibrating $\\pi$ pulses is one of the most fundamental operations in quantum computing because one of the most fundamental gates, the X gate, requires a $\\pi$ pulse input onto the X channel. Further, it also serves an important role in calibrating actual hardware.\n",
    "\n",
    "This tutorial will demonstrate how to calibrate a $\\pi$ pulse using Quanlse."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Preparation\n",
    "\n",
    "After successfully installing Quanlse, you could run the program below to calibrate the $\\pi$ pulses. To run this particular tutorial, you would need to import the following packages from Quanlse and other commonly-used Python libraries:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Import the Hamiltonian module\n",
    "from Quanlse.QHamiltonian import QHamiltonian as QHam \n",
    "\n",
    "# Import related packages\n",
    "from Quanlse.QOperator import duff, driveX\n",
    "from Quanlse.QWaveform import gaussian, QJob, QJobList\n",
    "\n",
    "# Import simulator interface for Quanlse Cloud Service\n",
    "from Quanlse.remoteSimulator import remoteSimulatorRunHamiltonian as runHamiltonian\n",
    "\n",
    "# Import numpy\n",
    "from numpy import linspace, pi, dot, array, cos\n",
    "\n",
    "# Import matplotlib\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "# Import curve_fit function from scipy\n",
    "from scipy.optimize import curve_fit"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Define the system Hamiltonian\n",
    "\n",
    "In the field of quantum control, it is a common practice to describe a quantum system with its Hamiltonian. Generally, a system Hamiltonian consists of two terms, the time-independent and the time-dependent terms:\n",
    "\n",
    "$$\n",
    "\\hat{H}_{\\rm total}(t) = \\hat{H}_{\\rm drift} + \\hat{H}_{\\rm ctrl }(t) .\n",
    "$$\n",
    "\n",
    "\n",
    "We start with a single-qubit system with three energy levels. The system Hamiltonian can be written as:\n",
    "\n",
    "$$\n",
    "\\hat{H} = \\alpha_q \\hat{a}^{\\dagger}\\hat{a}^{\\dagger}\\hat{a}\\hat{a} + \\frac{1}{2} c(t) \\cos(\\phi) (\\hat{a}+\\hat{a}^{\\dagger}).\n",
    "$$\n",
    "\n",
    "Here, $\\alpha_q$ is the anharmonicity between the two lowest transition energy levels. $c(t)$ indicates the pulse envelope function, and $\\phi$ is the pulse phase. $\\hat{a}^{\\dagger}=|1\\rangle\\langle 0|+\\sqrt{2}|2\\rangle\\langle 1|$ and $\\hat{a}=|0\\rangle\\langle 1|+\\sqrt{2}|1\\rangle\\langle 2|$ are respectively the creation and annihilation operators.\n",
    "\n",
    "Users could easily create the Hamiltonian object for this system using the `QHamiltonian` module in Quanlse:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ham = QHam(subSysNum=1, sysLevel=3, dt=0.2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The above `QHam()` function returns a Hamiltonian, including the number of qubits and their energy levels, and sampling period.\n",
    "\n",
    "Then we could add terms to this Hamiltonian. For example, the function `addDrift()` adds drift operators to the Hamiltonian. It basically requires a QHam object `ham`, the accordingly operators (we have provided the `QOperator` module which includes many commonly-used operators), the qubit(s) index(es) which the operators are acting upon, and the amplitude `coef`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "alphaQ = - 0.22 * (2 * pi)  # unit is GHz\n",
    "ham.addDrift(duff, 0, coef=alphaQ)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here we could conveniently use `QOperator`'s method `duff()` to define the $n$-dimensional $\\hat{a}^{\\dagger}\\hat{a}^{\\dagger}\\hat{a}\\hat{a}$.\n",
    "\n",
    "Then, the user could use the `print()` function to display the properties of this Hamiltonian:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(ham)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Next, we add the control terms to the Hamiltonian we created before by `addWave()`. Compared with the previous version of Quanlse, we have updated the strategy of adding control pulses by adding the operator and its accordingly waveform simultaneously. Here, we need to add the effective pulse:\n",
    "\n",
    "$$\n",
    "c(t) = A e^{-(t-\\tau)^2/2\\sigma^2}.\n",
    "$$\n",
    "\n",
    "The `addWave()` function takes the control term operator `driveX()`, the target qubit's index, and its waveform (Quanlse supports multiple waveforms' definitions) with parameters needed to define the wave."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ham.appendWave(driveX, 0, gaussian(t=20, a=1.0, tau=10.0, sigma=3.0))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So far, we have just defined a complete quantum system and the parameters regarding controlling the system. Then, we can plot the pulse jobs by `plot()` the QJob class of our Hamiltonian. The function also includes an optional bool parameter `dark`, which enables a dark-themed mode. Moreover, the user can use the `color` parameter to specify colors for individual pulses (the colors will repeat if there are more pulses than colors)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ham.job.plot(dark=True, color=['mint'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Then we can use the `simulate()` function to simulate the evolution and obtain some important information, including the unitary matrix of the system evolution."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "result = ham.simulate(recordEvolution=False)\n",
    "result.result"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Sweep amplitudes\n",
    "\n",
    "With fixed pulse duration $t_g$, we can sweep the pulse's amplitudes $a$, and find the amplitude $a_{\\pi}$ of the according $\\pi$ pulse.\n",
    "\n",
    "We first create a list of 200 points between -1 and 1, representing the pulse's amplitudes. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Initilize the pulse's amplitudes\n",
    "aList = linspace(-1.0, 1.0, 200)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Then, we can obtain the according population for each state by simulating the evolution of the Hamiltonian defined in the previous section. The calculation usually takes a long time to process on local devices; however, we provide a cloud computing service that could speed up this process significantly. To use Quanlse Cloud Service, the users can get a token from http://quantum-hub.baidu.com and submit the job onto Quanlse's server. Note that Quanlse supports the submission of batches of jobs, which could further optimize resource allocation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Calibrate a Pi Pulse\n",
    "jobList = ham.createJobList()\n",
    "for a in aList:\n",
    "    # Configure pulse parameters\n",
    "    job = jobList.createJob()\n",
    "    job.appendWave(driveX, 0, gaussian(20, a=a, tau=10, sigma=3))\n",
    "    jobList.addJob(job)\n",
    "\n",
    "# Import Define class and set the token\n",
    "# Please visit http://quantum-hub.baidu.com\n",
    "from Quanlse import Define\n",
    "Define.hubToken = \"\"\n",
    "\n",
    "# Submit batch jobs to Quanlse Cloud Service\n",
    "resultList = runHamiltonian(ham, jobList=jobList)\n",
    "\n",
    "# Calculate populations\n",
    "pop0List = []\n",
    "pop1List = []\n",
    "pop2List = []\n",
    "for result in resultList:\n",
    "    finalState = dot(result[\"unitary\"], array([1, 0, 0], dtype=complex))\n",
    "    pop0List.append(abs(finalState[0])**2)\n",
    "    pop1List.append(abs(finalState[1])**2)\n",
    "    pop2List.append(abs(finalState[2])**2)\n",
    "\n",
    "# Plot graph\n",
    "plt.plot(aList, pop0List, label=\"Ground state\")\n",
    "plt.plot(aList, pop1List, label=\"1st excited state\")\n",
    "plt.plot(aList, pop2List, label=\"2nd excited state\")\n",
    "plt.xlabel(\"Amplitude\")\n",
    "plt.ylabel(\"Population of different states\")\n",
    "plt.legend()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Cosine regression\n",
    "\n",
    "Now, we have a series of discrete points, and we need to fit those points with a cosine function to find the amplitude of the $\\pi$ pulse. To fit the resulting $|0\\rangle$ population, we use the `optimize.curve_fit()` method in `Scipy`. We first define the following function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def fit_function(xValues, yValues, initParams):\n",
    "    def fit_func(x, A, B, period, phi):\n",
    "        return A * cos(2 * pi * x / period - phi) + B\n",
    "    fitParams, _ = curve_fit(fit_func, xValues, yValues, initParams, bounds=(0, [2.0, 2.0, 2.0, 2.0]), method='dogbox')\n",
    "    yFit = fit_func(xValues, *fitParams)\n",
    "    return fitParams, yFit"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Then we run the regression function to obtain the result:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "fitParams, yFit = fit_function(aList, pop0List, [0.5, 0.5, 0.8, 0])\n",
    "\n",
    "# Plot graph\n",
    "plt.scatter(aList, pop0List, label=\"Samples\")\n",
    "plt.plot(aList, yFit, color=\"red\", label=\"Fit curve\")\n",
    "plt.xlabel(\"Amplitude\")\n",
    "plt.ylabel(\"Population of ground state\")\n",
    "plt.legend()\n",
    "plt.show()\n",
    "print(f\"Period is {fitParams[2]}\")\n",
    "print(f\"Pi pulse amplitude is {fitParams[2] / 2}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "By the cosine regression, we have identified the corresponding amplitude of the $\\pi$ pulse is around 0.42."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "source": [
    "## Summary\n",
    "\n",
    "After reading this tutorial on calibrating  𝜋  pulses, users could follow this link [tutorial-calibrate-pi-pulses.ipynb](https://github.com/baidu/Quanlse/blob/main/Tutorial/EN/tutorial-pi-pulse.ipynb) to the GitHub page of this Jupyter Notebook document and obtain the relevant codes for themselves. In addition, the users are encouraged to try parameter values different from this tutorial to understand Quanlse better."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
